fix(adk-middleware): strip nested required so Gemini accepts function declarations with nested object schemas#1664
Draft
tylerslaton wants to merge 1 commit into
Draft
fix(adk-middleware): strip nested required so Gemini accepts function declarations with nested object schemas#1664tylerslaton wants to merge 1 commit into
required so Gemini accepts function declarations with nested object schemas#1664tylerslaton wants to merge 1 commit into
Conversation
…on declarations with nested object schemas
Gemini's function-calling API silently rejects function declarations whose
parameter schema carries a `required` field below the top-level object —
e.g. on the `items.properties` of an array, or on a nested property's
own `properties`. The model emits no error event and no content events,
just RUN_STARTED → STATE_SNAPSHOT → RUN_FINISHED, which surfaces as a
frozen agent: the user sends a message, no chart/UI ever renders, no text
ever streams back.
The most common shape that trips this is Zod's `z.array(z.object({...}))`
when the inner object marks fields as required:
```json
{
"type": "object",
"required": ["data"],
"properties": {
"data": {
"type": "array",
"items": {
"type": "object",
"properties": {"label": {...}, "value": {...}},
"required": ["label", "value"] ← Gemini chokes on this
}
}
}
}
```
This shape is produced by every CopilotKit gen-UI demo that renders
structured data (charts, tables, lists of records). It's also what any
backend tool that takes an array of typed records will generate from
Pydantic / Zod / similar libraries. The bug has been latent since
`_clean_schema_for_genai` was introduced in #1349 — OpenAI-backed
consumers don't hit it because OpenAI's function-calling API accepts
`required` at every nesting level, and simpler ADK demos with flat
schemas don't have nested objects to begin with.
Fix: add a `_top_level` parameter to `_clean_schema_for_genai` (defaults
to True for the outermost call, False on every recursive call). When
False, drop any `required` key. Top-level `required` is preserved —
Gemini does accept it there.
Verified against the CopilotKit `gen-ui-tool-based` demo: before the
fix, `render_bar_chart` calls returned empty SSE streams; after the
fix, TOOL_CALL_START + TOOL_CALL_ARGS containing the structured chart
payload are emitted and the chart renders end-to-end.
Tests: four new regression tests in `TestCleanSchemaForGenai` cover
(1) top-level required preservation, (2) the array-of-objects shape,
(3) nested-object-property required, (4) arbitrary-depth required.
All 41 existing tests in `test_client_proxy_tool.py` continue to pass.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Contributor
Python Preview PackagesVersion
Install with uvAdd the TestPyPI index to your [[tool.uv.index]]
name = "testpypi"
url = "https://test.pypi.org/simple/"
explicit = trueThen install the packages you need: # Core SDK
uv add 'ag-ui-protocol==0.0.0.dev1778625358' --index testpypi
# Integrations (each already depends on the matching ag-ui-protocol preview)
uv add 'ag-ui-langgraph==0.0.0.dev1778625358' --index testpypi
uv add 'ag-ui-crewai==0.0.0.dev1778625358' --index testpypi
# NOTE: ag-ui-agent-spec depends on pyagentspec (git-only, not on PyPI).
# You will need to install pyagentspec separately from its git repo.
uv add 'ag-ui-agent-spec==0.0.0.dev1778625358' --index testpypi
uv add 'ag_ui_adk==0.0.0.dev1778625358' --index testpypi
uv add 'ag_ui_strands==0.0.0.dev1778625358' --index testpypiInstall with pippip install \
--index-url https://test.pypi.org/simple/ \
--extra-index-url https://pypi.org/simple/ \
ag-ui-protocol==0.0.0.dev1778625358
Commit: 1f0e2a2 |
@ag-ui/a2a-middleware
@ag-ui/a2ui-middleware
@ag-ui/event-throttle-middleware
@ag-ui/mcp-apps-middleware
@ag-ui/middleware-starter
@ag-ui/a2a
@ag-ui/adk
@ag-ui/ag2
@ag-ui/agno
@ag-ui/aws-strands
@ag-ui/claude-agent-sdk
@ag-ui/crewai
@ag-ui/langchain
@ag-ui/langgraph
@ag-ui/langroid
@ag-ui/llamaindex
@ag-ui/mastra
@ag-ui/pydantic-ai
@ag-ui/server-starter
@ag-ui/server-starter-all-features
@ag-ui/vercel-ai-sdk
create-ag-ui-app
@ag-ui/client
@ag-ui/core
@ag-ui/encoder
@ag-ui/proto
commit: |
Collaborator
|
Converting back to Draft after discussion with @tylerslaton - unclear whether this is still needed |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Gemini's function-calling API silently rejects function declarations whose parameter schema carries a
requiredfield below the top-level object — e.g. on theitems.propertiesof an array, or on a nested property's ownproperties. The model emits no error event and no content events, just RUN_STARTED → STATE_SNAPSHOT → RUN_FINISHED, which surfaces as a frozen agent: the user sends a message, no chart/UI ever renders, no text ever streams back.The most common shape that trips this is Zod's
z.array(z.object({...}))when the inner object marks fields as required:{ "type": "object", "required": ["data"], "properties": { "data": { "type": "array", "items": { "type": "object", "properties": {"label": {...}, "value": {...}}, "required": ["label", "value"] ← Gemini chokes on this } } } }This shape is produced by every CopilotKit gen-UI demo that renders structured data (charts, tables, lists of records). It's also what any backend tool that takes an array of typed records will generate from Pydantic / Zod / similar libraries. The bug has been latent since
_clean_schema_for_genaiwas introduced in #1349 — OpenAI-backed consumers don't hit it because OpenAI's function-calling API acceptsrequiredat every nesting level, and simpler ADK demos with flat schemas don't have nested objects to begin with.Fix: add a
_top_levelparameter to_clean_schema_for_genai(defaults to True for the outermost call, False on every recursive call). When False, drop anyrequiredkey. Top-levelrequiredis preserved — Gemini does accept it there.Verified against the CopilotKit
gen-ui-tool-baseddemo: before the fix,render_bar_chartcalls returned empty SSE streams; after the fix, TOOL_CALL_START + TOOL_CALL_ARGS containing the structured chart payload are emitted and the chart renders end-to-end.Tests: four new regression tests in
TestCleanSchemaForGenaicover (1) top-level required preservation, (2) the array-of-objects shape, (3) nested-object-property required, (4) arbitrary-depth required. All 41 existing tests intest_client_proxy_tool.pycontinue to pass.